#include "config.h"

#define DBG_SUBSYS S_LIBINTERFACE

#include "lich_api.h"

#include "license.h"
#include "lichbd.h"
#include "utils.h"
#include "dbg.h"

typedef enum {
        OP_NULL,
        OP_TOUCH,
        OP_COPY,
        OP_MKDIR,
        OP_LIST,
        OP_FIND,
        OP_UNLINK,
        OP_RMDIR,
        OP_STAT,
        OP_RENAME,
        OP_CAT,
        OP_WRITE,
        OP_APPLY,
        OP_ATTR_GET,
        OP_ATTR_LIST,
        OP_ATTR_SET,
        OP_ATTR_REMOVE,
        OP_TRUNCATE,
        OP_SPARE_SET,
        OP_SPARE_GET,
        OP_SPARE_REMOVE,
        OP_FSSTAT,
        OP_LIST_STORAGEAREA,
} admin_op_t;

int object_md5sum(const fileid_t *oid);

static void usage()
{
        fprintf(stderr, "\nusage:\n"
                "lichfs --stat <path>\n"
                "lichfs --touch <path> [-p 0|1]\n"
                "lichfs --list <path>\n"
                "lichfs --unlink <path>\n"
                "lichfs --rmdir <path>\n"
                "lichfs --find <path> [name]\n"
                "lichfs --copy :<src> <dist> [idx] [-p 0|1] [--single]\n"
                "lichfs --copy <src> [idx] :<dist> [--single]\n"
                "lichfs --copy <src> <dist> [-p 0|1] [--single]\n"
                "lichfs --stat <path> --pool <pool>\n"
                "lichfs --mkdir <path> --pool <pool> [-p] [-e k+r] [-j log:data]\n"
                "lichfs --touch <path> --pool <pool> [-p 0|1]\n"
                "lichfs --list <path> --pool <pool>\n"
                "lichfs --unlink <path> --pool <pool>\n"
                "lichfs --rmdir <path> --pool <pool>\n"
                "lichfs --find <path> [name] --pool <pool>\n"

                "lichfs --copy :<src> <dist> [idx] --pool <pool> [-p 0|1] [--single]\n"
                "lichfs --copy <src> [idx] :<dist> --pool <pool> [--single]\n"
                "lichfs --copy <src> <dist> --pool <pool> [-p 0|1] [--single]\n"

                //"lichfs --apply :<src> <dist>\n"
                "lichfs --rename <from> <to> --pool <pool>\n"
                "lichfs --cat <path> --pool <pool>\n"
                "lichfs --attrset <path> <key> <value> --pool <pool>\n"
                "lichfs --attrget <path> <key> --pool <pool>\n"
                "lichfs --attrlist <path> --pool <pool>\n"
                "lichfs --attrremove <path> <key> --pool <pool>\n"
                "lichfs --truncate <path> <length> --pool <pool>\n"
                "lichfs --fsstat --pool <pool>\n"
               );
}

static int __lich_xattr_set(const char *pool, const char *path, const char *key, const char *value)
{
        return lich_xattr_set(pool, path, key, value);
}

static int __lich_xattr_get(const char *pool, const char *path, const char *key)
{
        return lich_xattr_get(pool, path, key, 0);
}

static int __lich_xattr_list(const char *pool, const char *path)
{
        return lich_xattr_list(pool, path);
}

static int __lich_xattr_remove(const char *pool, const char *path, const char *key)
{
        return lich_xattr_remove(pool, path, key);
}

static int __getopt(int argc, char *argv[], char *arg)
{
        int i;

        for (i = 0; i < argc; i++) {
                if (!strcmp(arg, argv[i])) {
                        if (i + 1 < argc)
                                return i + 1;
                        break;
                }
        }

        return -1;
}

static int __getidx(int argc, char *argv[], char *arg)
{
        int i;

        for (i = 0; i < argc; i++) {
                if (!strcmp(arg, argv[i])) {
                        return i;
                }
        }

        return -1;
}

int main(int argc, char *argv[])
{
        int ret, op = OP_NULL, multithreading = 1;
        bool thin = false;
        char c_opt;
        int verbose = 0, idx = -1, parents = 0, priority = -1;
        off_t offset = 0;
        size_t size = 0;
        const char *from = NULL, *to = NULL, *path = NULL, *key = NULL, *value = NULL, *pool = NULL;

        char tmp[MAX_NAME_LEN], *dist[2];
        int count = 2;
        ec_t ec;
        uint32_t k = 0, r = 0;
        uint32_t log = 1, data = 10;

        dbg_info(0);

        (void) verbose;

        while (srv_running) {
                int option_index = 0;

                static struct option long_options[] = {
                        { "rename", required_argument, 0, 0},
                        { "cat", required_argument, 0, 0},
                        { "write", required_argument, 0, 0},
                        { "attrset", required_argument, 0, 0},
                        { "attrget", required_argument, 0, 0},
                        { "attrremove", required_argument, 0, 0},
                        { "fsstat", no_argument, 0, 0},
                        { "truncate", required_argument, 0, 0},
                        { "single", no_argument, 0, 0},
                        { "apply", required_argument, 0, 0},
                        { "thin", no_argument, 0, 0},
                        { "attrlist", required_argument, 0, 0},
                        { "liststoragearea", no_argument, 0, 0},
                        { "list", required_argument, 0, 0},
                        { "pool", required_argument, 0, 0},
                        { "stat", required_argument, 0, 's'},
                        { "mkdir", required_argument, 0, 'm'},
                        { "touch", required_argument, 0, 't'},
                        { "find", required_argument, 0, 'f'},
                        { "unlink", required_argument, 0, 'u'},
                        { "rmdir", required_argument, 0, 'r'},
                        { "copy", required_argument, 0, 'c'},
                        { "verbose", 0, 0, 'v' },
                        { "help",    0, 0, 'h' },
                        { 0, 0, 0, 0 },
                };

                c_opt = getopt_long(argc, argv, "s:c:m:t:f:u:r:npe:o:l:vhj:", long_options, &option_index);
                if (c_opt == -1)
                        break;

                switch (c_opt) {
                case 0:
                        switch (option_index) {
                        case 0:
                                op = OP_RENAME;
                                from = optarg;
                                to = argv[3];
                                break;
                        case 1:
                                op = OP_CAT;
                                path = optarg;
                                break;
                        case 2:
                                op = OP_WRITE;
                                path = argv[2];
                                value = argv[3];
                                break;
                        case 3:
                                op = OP_ATTR_SET;
                                path = argv[2];
                                key = argv[3];
                                value = argv[4];
                                break;
                        case 4:
                                op = OP_ATTR_GET;
                                path = argv[2];
                                key = argv[3];
                                break;
                        case 5:
                                op = OP_ATTR_REMOVE;
                                path = argv[2];
                                key = argv[3];
                                break;
                        case 6:
                                op = OP_FSSTAT;
                                //path = argv[2];
                                break;
                        case 7:
                                op = OP_TRUNCATE;
                                path = argv[2];
                                value = argv[3];
                                break;
                        case 8:
                                multithreading = 0;
                                break;
                        case 9:
                                op = OP_APPLY;
                                from = optarg;
                                to = argv[3];
                                break;
                        case 10:
                                thin = true;
                                break;
                        case 11:
                                op = OP_ATTR_LIST;
                                path = argv[2];
                                break;
                        case 12:
                                op = OP_LIST_STORAGEAREA;
                                break;
                        case 13:
                                op = OP_LIST;
                                key = optarg;
                                break;
                        case 14:
                                pool = optarg;
                                break;
                        default:
                                fprintf(stderr, "Hoops, wrong op got!\n");
                                YASSERT(0);
                        }

                        break;
                case 's':
                        op = OP_STAT;
                        key = optarg;
                        break;
                case 'm':
                        op = OP_MKDIR;
                        key = optarg;
                        break;
                case 't':
                        op = OP_TOUCH;
                        key = optarg;
                        break;
                case 'f':
                        op = OP_FIND;
                        path = optarg;
                        if (argc >= 4 && strncmp(argv[3], "--pool", 6))
                                key = argv[3];
                        break;
                case 'r':
                        op = OP_RMDIR;
                        key = optarg;
                        break;
                case 'u':
                        op = OP_UNLINK;
                        key = optarg;
                        break;
                case 'c':
                        op = OP_COPY;
                        from = optarg;

                        int _argc;

                        _argc = argc;
                        if (__getidx(argc, argv, "--single") != -1) {
                                _argc--;
                        }

                        if (__getopt(argc, argv, "-p") != -1) {
                                _argc -= 2;
                        }

                        if (__getopt(argc, argv, "--pool") != -1) {
                                _argc -= 2;
                        }

                        if (_argc == 5) {
                                if (from[0] == ':') {
                                        to = argv[3];
                                        idx = atoi(argv[4]);
                                } else if (from[0] == '/') {
                                        to = argv[4];
                                        idx = atoi(argv[3]);
                                }
                        } else {
                                to = argv[3];
                        }

                        if ((from[0] != ':' && from[0] != '/') ||
                                        (to[0] != ':' && to[0] != '/')) {
                                usage();
                                EXIT(EINVAL);
                        }

                        break;
                case 'p':
                        parents = 1;
                        int i = __getopt(argc, argv, "-p");
                        if (i >= 0) {
                                if (!is_zero_one_char(argv[i])) {
                                        usage();
                                        EXIT(EINVAL);
                                }
                                priority = atoi(argv[i]);
                        }
                        break;
                case 'e':
                        strcpy(tmp, optarg);
                        _str_split(tmp, '+', dist, &count);
                        if (count != 2) {
                                usage();
                                EXIT(EINVAL);
                        }

                        if (!_str_isdigit(dist[0]) || !_str_isdigit(dist[1])) {
                                usage();
                                EXIT(EINVAL);
                        }

                        ret = sscanf(optarg, "%u+%u", &k, &r);
                        if (ret != 2 || !k || !r) {
                                usage();
                                EXIT(EINVAL);
                        }
                        break;

                case 'j':
                        strcpy(tmp, optarg);
                        _str_split(tmp, ':', dist, &count);
                        if (count != 2) {
                                usage();
                                EXIT(EINVAL);
                        }

                        if (!_str_isdigit(dist[0]) || !_str_isdigit(dist[1])) {
                                usage();
                                EXIT(EINVAL);
                        }

                        ret = sscanf(optarg, "%u:%u", &log, &data);
                        if (ret != 2) {
                                usage();
                                EXIT(EINVAL);
                        }
                        break;

                case 'o':
                        key = optarg;
                        break;
                case 'l':
                        value = optarg;
                        break;
                case 'v':
                        verbose = 1;
                        break;
                case 'h':
                        usage();
                        EXIT(0);
                default:
                        usage();
                        EXIT(EINVAL);
                }
        }

#if 1
        if (argc == 1) {
                usage();
                exit(EINVAL);
        }
#endif

#if 0
        ret = env_init_simple("lich");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = network_connect_master();
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        ret = stor_init(NULL, -1);
        if (unlikely(ret))
                GOTO(err_ret, ret);
#endif

        ret = lichbd_init("");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if ((op == OP_COPY) || (op == OP_ATTR_SET) || (op == OP_ATTR_REMOVE)) {
                ret = storage_license_check();
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        switch (op) {
        case OP_MKDIR:
                ec.plugin = (!k || !r) ? PLUGIN_NULL : PLGUIN_EC_ISA;
                ec.tech = (!k || !r) ? TECH_NULL : TECH_ISA_SSE;
                ec.k = k;
                ec.m = k + r;

                ec.log_size = log;
                ec.data_size = data;

                if (ec.plugin == PLGUIN_EC_ISA) {
                        if (ec.k <= 0 || ec.m <= 0 || ec.k > EC_KMAX || ec.m > EC_MMAX) {
                                ret = EPERM;
                                GOTO(err_ret, ret);
                        }
                }

                ret = utils_mkdir(pool ? pool : "default", key, parents, &ec, "");
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_TOUCH:
                ret = utils_touch(pool ? pool : "default", key, priority);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_RMDIR:
                ret = utils_rmdir(pool ? pool : "default", key);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_UNLINK:
                ret = utils_rmvol(pool ? pool : "default", key, 0);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_LIST:
                ret = utils_list(pool ? pool : "default", key, 2, 0);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_FIND:
                ret = utils_find(pool ? pool : "default", path, key, 0);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_STAT:
                ret = utils_stat(pool ? pool : "default", key, 0);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_COPY:
                if (idx == -1) {
                        ret = stor_copy(pool ? pool : "default", from, to, multithreading, thin, priority);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);
                } else {
                        ret = stor_copy_offset(pool ? pool : "default", from, to, idx);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);
                }

                break;
        case OP_RENAME:
                ret = utils_rename(pool ? pool : "default", from, to);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                break;
        case OP_CAT:
                offset = key ? atol(key) : 0;
                size = value ? atol(value) : 0;
                ret = utils_cat(pool ? pool : "default", path, offset, size);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                break;
        case OP_WRITE:
                offset = key ? atol(key) : 0;
                ret = utils_write(pool ? pool : "default", value, path, offset);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                break;
        case OP_APPLY:
                ret = stor_apply(pool ? pool : "default", from, to);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                break;
        case OP_ATTR_GET:
                ret = __lich_xattr_get(pool ? pool : "default", path, key);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                break;
        case OP_ATTR_SET:
                ret = __lich_xattr_set(pool ? pool : "default", path, key, value);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                break;
        case OP_ATTR_LIST:
                ret = __lich_xattr_list(pool ? pool : "default", path);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                break;
        case OP_ATTR_REMOVE:
                ret = __lich_xattr_remove(pool ? pool : "default", path, key);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                break;
        case OP_FSSTAT:
                ret = utils_fsstat(pool ? pool : "default");
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                break;
        case OP_TRUNCATE:
                if (!value) {
                        ret = EINVAL;
                        GOTO(err_ret, ret);
                }

                ret = utils_get_size(value, &size);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                ret = utils_truncate(pool ? pool : "default", path, size);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                printf("truncate %s to %s\n", path, value);
                break;
        case OP_LIST_STORAGEAREA:
                ret = utils_list_storagearea();
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                break;
        default:
                usage();
                exit(EINVAL);
        }

        return 0;
err_ret:
        fprintf(stderr, "error: %s\n", strerror(ret));
        EXIT(_errno(ret));
}
